dataviz\figure\canvas/pixelcanvas.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286
use ab_glyph::{FontRef, PxScale};
use image::Rgb;
use imageproc::drawing::{draw_text_mut, text_size};
use crate::figure::utilities::linetype::LineType;
/// A structure representing a pixel-based drawing canvas.
pub struct PixelCanvas {
/// Width of the canvas in pixels.
pub width: u32,
/// Height of the canvas in pixels.
pub height: u32,
/// Background color of the canvas (RGB format).
pub background_color: [u8; 3],
/// Buffer storing pixel data as a linear array.
pub buffer: Vec<u8>,
/// Margin around the canvas (in pixels).
pub margin: u32,
}
impl PixelCanvas {
/// Creates a new `PixelCanvas` with the specified dimensions, background color, and margin.
///
/// # Parameters
/// - `width`: The width of the canvas in pixels.
/// - `height`: The height of the canvas in pixels.
/// - `background_color`: The RGB color of the canvas background.
/// - `margin`: Margin size in pixels.
///
/// # Returns
/// A new `PixelCanvas` instance.
pub fn new(width: u32, height: u32, background_color: [u8; 3], margin: u32) -> Self {
let buffer = vec![0; (width * height * 3) as usize];
Self {
width,
height,
background_color,
buffer,
margin,
}
}
/// Clears the canvas by filling it with the background color.
pub fn clear(&mut self) {
self.buffer.fill(self.background_color[0]);
}
/// Draws a single pixel at the specified coordinates with the given color.
///
/// # Parameters
/// - `x`: The x-coordinate of the pixel.
/// - `y`: The y-coordinate of the pixel.
/// - `color`: The RGB color of the pixel.
pub fn draw_pixel(&mut self, x: u32, y: u32, color: [u8; 3]) {
let index = ((y * self.width + x) * 3) as usize;
if index + 2 < self.buffer.len() {
self.buffer[index] = color[0];
self.buffer[index + 1] = color[1];
self.buffer[index + 2] = color[2];
}
}
/// Blends a pixel with the specified color and alpha value.
///
/// # Parameters
/// - `x`: The x-coordinate of the pixel.
/// - `y`: The y-coordinate of the pixel.
/// - `color`: The RGB color to blend.
/// - `alpha`: The transparency value (0.0 to 1.0).
pub fn blend_pixel(&mut self, x: u32, y: u32, color: [u8; 3], alpha: f64) {
let index = ((y * self.width + x) * 3) as usize;
if index + 2 < self.buffer.len() {
let existing_color = [
self.buffer[index],
self.buffer[index + 1],
self.buffer[index + 2],
];
let blended_color = [
(color[0] as f64 * alpha + existing_color[0] as f64 * (1.0 - alpha)) as u8,
(color[1] as f64 * alpha + existing_color[1] as f64 * (1.0 - alpha)) as u8,
(color[2] as f64 * alpha + existing_color[2] as f64 * (1.0 - alpha)) as u8,
];
self.buffer[index] = blended_color[0];
self.buffer[index + 1] = blended_color[1];
self.buffer[index + 2] = blended_color[2];
}
}
/// Draws a horizontal line at the specified y-coordinate.
///
/// # Parameters
/// - `y`: The y-coordinate of the line.
/// - `color`: The RGB color of the line.
pub fn draw_horizontal_line(&mut self, y: u32, color: [u8; 3]) {
for x in self.margin..self.width - self.margin {
self.draw_pixel(x, y, color);
}
}
/// Draws a vertical line at the specified x-coordinate.
///
/// # Parameters
/// - `x`: The x-coordinate of the line.
/// - `color`: The RGB color of the line.
pub fn draw_vertical_line(&mut self, x: u32, color: [u8; 3]) {
for y in self.margin..self.height - self.margin {
self.draw_pixel(x, y, color);
}
}
/// Draws a grid on the canvas.
///
/// # Parameters
/// - `grid_size`: An array specifying the spacing of grid lines in the x and y directions.
/// - `color`: The RGB color of the grid lines.
pub fn draw_grid(&mut self, grid_size: &[usize; 2], color: [u8; 3]) {
for x in (self.margin..=self.width - self.margin).step_by(grid_size[0]) {
self.draw_vertical_line(x, color);
}
for y in (self.margin..=self.height - self.margin).step_by(grid_size[1]) {
self.draw_horizontal_line(y, color);
}
}
/// Draws text vertically at the specified position.
///
/// # Parameters
/// - `x`: The x-coordinate for the text.
/// - `y`: The y-coordinate for the text.
/// - `text`: The text content.
/// - `color`: The RGB color of the text.
/// - `font`: The font reference for rendering text.
/// - `scale`: The scaling factor for the font size.
pub fn draw_text_vertical(
&mut self,
x: u32,
y: u32,
text: &str,
color: [u8; 3],
font: &ab_glyph::FontRef,
scale: ab_glyph::PxScale,
) {
let mut current_y = y;
// Draw each character vertically
for ch in text.chars() {
let char_as_str = ch.to_string();
let (_char_width, char_height) = text_size(scale, font, &char_as_str);
// Draw the character
self.draw_text(x, current_y, &char_as_str, color, font, scale);
// Move down for the next character
current_y += char_height as u32 + 5; // Adjust spacing between characters
}
}
/// Draws text at the specified position.
///
/// # Parameters
/// - `x`: The x-coordinate for the text.
/// - `y`: The y-coordinate for the text.
/// - `text`: The text content.
/// - `color`: The RGB color of the text.
/// - `font`: The font reference for rendering text.
/// - `scale`: The scaling factor for the font size.
pub fn draw_text(
&mut self,
x: u32,
y: u32,
text: &str,
color: [u8; 3],
font: &FontRef,
scale: PxScale,
) {
let img: &mut [u8] = &mut self.buffer;
let mut buffer =
image::ImageBuffer::from_raw(self.width as u32, self.height as u32, img.to_vec())
.unwrap();
draw_text_mut(
&mut buffer,
Rgb(color),
x as i32,
y as i32,
scale,
&font,
text,
);
self.buffer = buffer.into_raw();
}
/// Draws a line with the specified type (solid, dashed, or dotted).
///
/// # Parameters
/// - `x1`, `y1`: Coordinates of the start point.
/// - `x2`, `y2`: Coordinates of the end point.
/// - `color`: The RGB color of the line.
/// - `line_type`: The type of line to draw (`LineType`).
pub fn draw_line(
&mut self,
x1: i32,
y1: i32,
x2: i32,
y2: i32,
color: [u8; 3],
line_type: LineType,
) {
let dx = (x2 - x1).abs();
let dy = -(y2 - y1).abs();
let sx = if x1 < x2 { 1 } else { -1 };
let sy = if y1 < y2 { 1 } else { -1 };
let mut err = dx + dy;
let mut x = x1;
let mut y = y1;
match line_type {
LineType::Solid => {
// Draw a continuous line without any gaps
while x != x2 || y != y2 {
self.draw_pixel(x as u32, y as u32, color);
let e2 = 2 * err;
if e2 >= dy {
err += dy;
x += sx;
}
if e2 <= dx {
err += dx;
y += sy;
}
}
// Draw the final pixel
self.draw_pixel(x2 as u32, y2 as u32, color);
}
LineType::Dashed(dash_length) | LineType::Dotted(dash_length) => {
let mut is_drawing = true;
let mut segment_length = 0;
while x != x2 || y != y2 {
if is_drawing {
self.draw_pixel(x as u32, y as u32, color);
}
segment_length += 1;
if segment_length == dash_length {
is_drawing = !is_drawing; // Toggle drawing
segment_length = 0; // Reset segment length
}
let e2 = 2 * err;
if e2 >= dy {
err += dy;
x += sx;
}
if e2 <= dx {
err += dx;
y += sy;
}
}
// Ensure the final pixel is drawn in drawing mode
if is_drawing {
self.draw_pixel(x2 as u32, y2 as u32, color);
}
}
}
}
/// Saves the current canvas as an image file.
///
/// # Parameters
/// - `file_path`: The path to save the image file.
///
/// # Panics
/// Panics if the image cannot be saved.
pub fn save_as_image(&self, file_path: &str) {
use image::{ImageBuffer, RgbImage};
let img: RgbImage = ImageBuffer::from_raw(self.width, self.height, self.buffer.clone())
.expect("Failed to create image buffer");
img.save(file_path).expect("Failed to save image");
}
}